<?php

namespace App\Common\Service;

use App\Common\Util\TP;
use Sc\Util\ImitateAopProxy\AopProxyTrait;
use Sc\Util\Tool;
use think\facade\App;

/**
 * Class PluginsService
 */
class PluginsService
{
    use AopProxyTrait;

    protected const PLUGINS_DIR = "/plugins/";
    protected const PLUGINS_RESOURCE_DIR = "/runtime/plugins/";
    protected const PLUGINS_CONFIG_DIR = "/config/plugins/";
    protected const BACK_URL = [
        'https://gitee.com/chenlong_sd/tpPlugins-%s/repository/archive/master.zip'
    ];

    /**
     * 安装
     * 1、文件检测，不存在则下载
     * 2、存在配置文件则复制配置文件到config/autoload/plugins/文件夹
     * 4、数据表初始化
     * 5、如存在Install.php文件则执行 其静态run函数 > Install:run()
     *   Install::run 可执行自己的安装逻辑，比如代理系统默认逻辑类，增加额外数据库数据
     * @param string $name
     *
     * @return void
     * @throws \Exception
     */
    public function install(string $name): void
    {
        if (!$this->fileCheck($name)) {
            $this->getPlugins($name);
        }

        $this->copyConfig($name);
        $this->tableInit($name);
        $this->runPluginsInstall($name);
    }

    /**
     * 卸载
     * 卸载不会清除数据库
     * @param string $name
     *
     * @return void
     */
    public function uninstall(string $name): void
    {
        $this->runPluginsUninstall($name);

        if (is_file($this->getPath(self::PLUGINS_CONFIG_DIR . "$name.php"))) {
            unlink($this->getPath(self::PLUGINS_CONFIG_DIR . "$name.php"));

            $this->clearConfig($name);
        }

        if ($this->fileCheck($name)) {
            Tool::dir($this->getPath(self::PLUGINS_DIR . $name))->remove();
        }

    }

    /**
     * @param string $path
     * @return string
     */
    private function getPath(string $path): string
    {
        return preg_replace('#[/\\\]+#', DIRECTORY_SEPARATOR, strtr(App::getRootPath() . $path, ['/' => DIRECTORY_SEPARATOR]));
    }

    private function fileCheck(string $name): bool
    {
        return is_dir($this->getPath(self::PLUGINS_DIR . $name));
    }

    /**
     * @param string $name
     *
     * @return void
     * @throws \Exception
     */
    private function getPlugins(string $name): void
    {
        $this->download($name);

        $this->decompress($name);
    }

    /**
     * @param string $name
     *
     * @return void
     * @throws \Exception
     */
    private function download(string $name): void
    {
        $plugins = $this->getPluginsConfig($name);

        if (is_file($this->getPath(self::PLUGINS_RESOURCE_DIR . "$name.zip"))) {
            return;
        }

        if (!is_dir($this->getPath(self::PLUGINS_RESOURCE_DIR))) {
            mkdir($this->getPath(self::PLUGINS_RESOURCE_DIR), 0777, true);
        }

        try {
            foreach ([$plugins['url'] ?? '', ...self::BACK_URL] as $url) {
                if (empty($url)) continue;
                if ($fg = fopen(sprintf($url, $name), 'r')){
                    break;
                }
            }

            if (empty($fg)){
                throw new \Exception('插件获取失败');
            }

            $fw = fopen($this->getPath(self::PLUGINS_RESOURCE_DIR . "$name.zip"), 'w');

            while ($content = fread($fg, 8192)) {
                fwrite($fw, $content);
            }
        } catch (\Throwable $throwable) {
            throw new \Exception('插件下载失败， Message:' . $throwable->getMessage());
        } finally {
            empty($fg) or @fclose($fg);
            empty($fw) or @fclose($fw);
        }
    }

    /**
     * @param string $name
     *
     * @return mixed
     * @throws \Exception
     */
    private function getPluginsConfig(string $name): mixed
    {
        $plugins = array_filter(TP::config('plugins.lists'), fn($plugins) => $plugins['name'] === $name);

        return current($plugins);
    }

    /**
     * @param string $name
     *
     * @return void
     * @throws \Exception
     */
    private function decompress(string $name): void
    {
        $filename = self::PLUGINS_RESOURCE_DIR . "$name.zip";

        $zipArchive = new \ZipArchive();

        if (!$zipArchive->open($filename)) {
            throw new \Exception('文件解压失败');
        }

        $extractPath = $this->getPath(self::PLUGINS_RESOURCE_DIR . $name);
        try {
            $zipArchive->extractTo($extractPath);

            $dirs = Tool::dir($extractPath)->getDirs();

            $usePath = $this->getPath(self::PLUGINS_DIR . $name);

            if (!is_dir($usePath)){
                mkdir($usePath, 0777, true);
            }

            rename($this->getPath(self::PLUGINS_RESOURCE_DIR . "$name/$dirs[0]"), $usePath);
        } catch (\Throwable $throwable) {
            throw new \Exception('文件解压失败, error message: ' . $throwable->getMessage());
        } finally {
            $zipArchive->close();
            Tool::dir($extractPath)->remove();
        }
    }

    /**
     * @param string $name
     *
     * @return void
     */
    private function copyConfig(string $name): void
    {
        $configPath = $this->getPath(self::PLUGINS_DIR . "$name/config.php");
        if (!is_file($configPath)) {
            return;
        }

        $pluginsConfigDirPath = $this->getPath(self::PLUGINS_CONFIG_DIR);
        if (!is_dir($pluginsConfigDirPath)) {
            mkdir($pluginsConfigDirPath, 0777, true);
        }

        $pluginsUsePath = $this->getPath(self::PLUGINS_CONFIG_DIR . "$name.php");
        if (!is_file($pluginsUsePath)) {
            copy($configPath, $pluginsUsePath);
        }

        $pluginsConfigPath = $this->getPath("/config/plugins.php");
        $pluginsConfig = file_get_contents($pluginsConfigPath);

        if (!str_contains($pluginsConfig, "'$name' => ")) {
            $pluginsConfig = preg_replace("/(\r?\n)];\s*$/", "\n    '$name' => include __DIR__ . '/plugins/$name.php',$0", $pluginsConfig);
            file_put_contents($pluginsConfigPath, $pluginsConfig);
        }
    }

    private function clearConfig(string $name): void
    {
        $pluginConfigPath = $this->getPath("/config/plugins.php");
        $pluginsConfig = file_get_contents($pluginConfigPath);

        if (str_contains($pluginsConfig, "'$name' => ")) {
            $pluginsConfig = preg_replace("/^\s+'$name' => .*$/m", "", $pluginsConfig);
            file_put_contents($pluginConfigPath, $pluginsConfig);
        }
    }

    /**
     * @param string $name
     *
     * @return void
     */
    private function runPluginsInstall(string $name): void
    {
        $installFilePath = $this->getPath(self::PLUGINS_DIR . "$name/Install.php");
        if (!is_file($installFilePath)) {
            return;
        }
        if (!class_exists("Plugins\\$name\\Install")){
            include $installFilePath;
        }

        forward_static_call("Plugins\\$name\\Install::run");
    }

    /**
     * @param string $name
     * @return void
     */
    private function runPluginsUninstall(string $name): void
    {
        $uninstallFilePath = $this->getPath(self::PLUGINS_DIR . "$name/Uninstall.php");
        if (!is_file($uninstallFilePath)) {
            return;
        }
        if (!class_exists("Plugins\\$name\\Uninstall")){
            include $uninstallFilePath;
        }
        forward_static_call("Plugins\\$name\\Uninstall::run");
    }

    /**
     * @param string $name
     *
     * @return void
     * @throws \Exception
     */
    private function tableInit(string $name): void
    {
        $modelPath = $this->getPath(self::PLUGINS_DIR . "$name/Common/Model");
        if (!is_dir($modelPath)) {
            return;
        }
        $init = TableInitSqlService::aop(false)->extractInitData($modelPath);

        $errorMsg = [];
        $init->runCreateSql($errorMsg);
        $init->runInitData($errorMsg);

        if ($errorMsg) {
            throw new \Exception(implode(PHP_EOL, $errorMsg));
        }
    }
}